#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "cutil_math.h"
#include "scene.h"
#include "objects.h"
#include "parse.h"

#define BUFFER_SIZE 1024
#define PI 3.14159265358979323846264338327950288f

void parseError(const char* infile, int line)
{
	fprintf(stderr, "Error parsing %s on line %d\n", infile, line);
	exit(line);
}
	
void parseMesh(scene *s, char *filename, int sf_id, float4 (&m)[4], float scale)
{
	char buffer[BUFFER_SIZE];
	int n_vertices, n_triangles;
	float3 mins, maxs;
	vertex *vertices;
	tri *triangles;
	
	FILE *f = fopen(filename, "r");
	if(!f)
	{
		fprintf(stderr, "couldn't open %s.\n", filename);
		exit(-20);
	}
	
	// ignore comments
	while(Fgets(buffer, BUFFER_SIZE, f))
		if(buffer[0] != '#')
			break;
	
	n_vertices = atoi(buffer);
	vertices = (vertex *) malloc(n_vertices * sizeof(vertex));
	
	// store each of the vertices locations
	for(int i = 0; i < n_vertices; i++)
	{
		Fgets(buffer, BUFFER_SIZE, f);
		if(buffer[0] == '#')
		{
			i--;
			continue;
		}
		float4 tmp_point;
		sscanf(buffer, "%f %f %f", &tmp_point.x, &tmp_point.y, &tmp_point.z);
		tmp_point.w = 1.0;
		
		// scale the point
		tmp_point *= scale;
		
		// rotate it
		vertices[i].point.x = dot(tmp_point, m[0]);
		vertices[i].point.y = dot(tmp_point, m[1]);
		vertices[i].point.z = dot(tmp_point, m[2]);
		
		// the vertex isn't associated with a triangle yet
		vertices[i].n = NULL;
		
		// to create a box around the mesh, get the max and min in each direction
		// store the first vertex regardless
		if(i == 0)
		{
			mins = make_float3(vertices[i].point.x,
			                   vertices[i].point.y,
			                   vertices[i].point.z);
			maxs = mins;
		}
		
		// otherwise store it if it is lower than the min or higher than the max
		else
		{
			if(vertices[i].point.x < mins.x)
				mins.x = vertices[i].point.x;
			if(vertices[i].point.y < mins.y)
				mins.y = vertices[i].point.y;
			if(vertices[i].point.z < mins.z)
				mins.z = vertices[i].point.z;
			if(vertices[i].point.x > maxs.x)
				maxs.x = vertices[i].point.x;
			if(vertices[i].point.y > maxs.y)
				maxs.y = vertices[i].point.y;
			if(vertices[i].point.z > maxs.z)
				maxs.z = vertices[i].point.z;
		}
	}
	s->m[s->n_ms - 1].b.mins = mins;
	s->m[s->n_ms - 1].b.maxs = maxs;
	
	// ignore comments
	while(Fgets(buffer, BUFFER_SIZE, f))
		if(buffer[0] != '#')
			break;
	
	// get the number of triangles
	n_triangles = atoi(buffer);
	s->m[s->n_ms - 1].n_ts = n_triangles;
	triangles = (tri *) malloc(n_triangles * sizeof(tri));
	
	// get the triangles vertex ids as well as its surface id
	if(sf_id == -1)
	{
		// store the vertex ids for each triangle
		for(int i = 0; i < n_triangles; i++)
		{
			Fgets(buffer, BUFFER_SIZE, f);
			if(buffer[0] == '#')
			{
				i--;
				continue;
			}
			sscanf(buffer, "%d %d %d %d", (int *) &triangles[i].ps[0],
			                              (int *) &triangles[i].ps[1],
			                              (int *) &triangles[i].ps[2],
			                              &triangles[i].sf_id);
		}
	}
	// use the provided sf_id for the triangles surface
	else
	{
		// store the vertex ids for each triangle
		for(int i = 0; i < n_triangles; i++)
		{
			Fgets(buffer, BUFFER_SIZE, f);
			if(buffer[0] == '#')
			{
				i--;
				continue;
			}
	
			sscanf(buffer, "%d %d %d", (int *) &triangles[i].ps[0],
			                           (int *) &triangles[i].ps[1],
			                           (int *) &triangles[i].ps[2]);
			triangles[i].sf_id = sf_id;
		}
	}
	
	fclose(f);
	node *p;
	
	// replace vertex ids with pointers to the vertices
	for(int i = 0; i < n_triangles; i++)
		for(int j = 0; j < 3; j++)
			triangles[i].ps[j] = &(vertices[((int) triangles[i].ps[j]) - 1]);
	
	// we now have a list of triangles with pointers to each of the vertices
	for(int i = 0; i < n_triangles; i++)
	{
		// for each vertex, store the address of each triangle using a linked list
		for(int j = 0; j < 3; j++)
		{
			p = triangles[i].ps[j]->n;
			
			// we're the first triangle to get to the node
			if(p == NULL)
			{
				triangles[i].ps[j]->n = (node *) malloc(sizeof(node));
				triangles[i].ps[j]->n->next = NULL;
				triangles[i].ps[j]->n->t = &triangles[i];
			}
			
			// another triangle was already here
			else
			{
				while(p->next)
					p = p->next;
				p->next = (node *) malloc(sizeof(node));
				p = p->next;
				p->next = NULL;
				p->t = &triangles[i];
			}
		}
		
		
		// store the vertexes in each of the triangles
		for(int j = 0; j < 3; j++)
			triangles[i].points[j] = triangles[i].ps[j]->point;
		
		// convert points[1] and points[2] to vectors from points[0]
		triangles[i].points[1] -= triangles[i].points[0];
		triangles[i].points[2] -= triangles[i].points[0];
		
		// calculate and store the triangle's normal
		triangles[i].normal = normalize(cross(triangles[i].points[1],
		                                      triangles[i].points[2]));

	}
	
	// for each of the vertices, average the neighboring triangles normals
	for(int i = 0; i < n_vertices; i++)
	{
		float3 normal = make_float3(0.0);
		p = vertices[i].n;
		while(p)
		{
			normal += p->t->normal;
			p = p->next;
		}
		normal = normalize(normal);
		vertices[i].normal = normal;
	}
	
	// store the vertex normals in each of the triangles
	for(int i = 0; i < n_triangles; i++)
		for(int j = 0; j < 3; j++)
			triangles[i].normals[j] = triangles[i].ps[j]->normal;


	// free the linked lists on all of the vertices
	for(int i = 0; i < n_vertices; i++)
	{
		node *old, *p;
		p = vertices[i].n;
		while(p)
		{
			old = p;
			p = p->next;
			free(old);
		}
	}
	
	// copy the data into cuda form
	s->m[s->n_ms - 1].t = (m_triangle *) malloc(sizeof(m_triangle) * n_triangles);

	for(int i = 0; i < n_triangles; i++)
		for(int j = 0; j < sizeof(m_triangle) / sizeof(float); j++)
			*(((float *) s->m[s->n_ms - 1].t) + n_triangles * j + i) =
			*(((float *) &triangles[i]) + j);

	free(vertices);
	free(triangles);
}



void parseScene(scene *s)
{
	char buffer[BUFFER_SIZE];
	int line = 0;
	
	FILE *fp = fopen(s->infile, "r");
	if(!fp)
	{
		fprintf(stderr, "Could not open %s for parsing\n", s->infile);
		exit(-2);
	}
	
	// get camera
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("CAMERA", buffer) != 0)
			continue;
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->c.cam_mat[0].w,
		                           &s->c.cam_mat[1].w,
		                           &s->c.cam_mat[2].w);
		
		float x, y, z;
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &x,
		                           &y,
		                           &z);
		
		// compute camera matrix
		make_matrix4(s->c.cam_mat, x, y, z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->c.fovy);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->c.x_res);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->c.y_res);

		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->c.max_bounces);

		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->c.aa);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->refract_idx);
		
		// set the anti aliasing squared
		s->c.aa2 = s->c.aa * s->c.aa;
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->grid_w);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->grid_h);
		
		break;
	}

	// lights
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("LIGHTS", buffer) == 0)
			break;
	}
	
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("SURFACES", buffer) == 0)
			break;
		
		if(nstrcmp("LIGHT", buffer) != 0)
			continue;
		
		s->n_ls++;
		if(s->n_ls == 1)
			s->l = (light *) malloc(sizeof(light));
		else
			s->l = (light *) realloc(s->l, s->n_ls * sizeof(light));
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->l[s->n_ls - 1].point.x,
		                           &s->l[s->n_ls - 1].point.y,
		                           &s->l[s->n_ls - 1].point.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->l[s->n_ls - 1].intensity.x,
		                           &s->l[s->n_ls - 1].intensity.y,
		                           &s->l[s->n_ls - 1].intensity.z);
	}
	
	// sufaces
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("MESHES", buffer) == 0)
			break;
		
		if(nstrcmp("SURFACE", buffer) != 0)
			continue;
		
		s->n_sfs++;
		if(s->n_sfs == 1)
			s->sf = (surface *) malloc(sizeof(surface));
		else
			s->sf = (surface *) realloc(s->sf, s->n_sfs * sizeof(surface));
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->sf[s->n_sfs - 1].ambient.x,
		                           &s->sf[s->n_sfs - 1].ambient.y,
		                           &s->sf[s->n_sfs - 1].ambient.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->sf[s->n_sfs - 1].diffuse.x,
		                           &s->sf[s->n_sfs - 1].diffuse.y,
		                           &s->sf[s->n_sfs - 1].diffuse.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->sf[s->n_sfs - 1].specular.x,
		                           &s->sf[s->n_sfs - 1].specular.y,
		                           &s->sf[s->n_sfs - 1].specular.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->sf[s->n_sfs - 1].specular_exp);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->sf[s->n_sfs - 1].reflect);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->sf[s->n_sfs - 1].refract);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->sf[s->n_sfs - 1].opacity);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->sf[s->n_sfs - 1].width);
	}
	
	// meshes
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("TRIANGLES", buffer) == 0)
			break;
		
		if(nstrcmp("MESH", buffer) != 0)
			continue;
		
		s->n_ms++;
		if(s->n_ms == 1)
			s->m = (f_mesh *) malloc(sizeof(f_mesh));
		else
			s->m = (f_mesh *) realloc(s->m, s->n_ms * sizeof(f_mesh));
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		
		//put in a null character to copy the filename
		int l=0;
		while(buffer[l] != ' ' && buffer[l] != '\n' && buffer[l] != '\0')
			l++;
		buffer[l] = '\0';
		char *f = (char *) malloc(sizeof(char) * (l + 1));
		strcpy(f, buffer);
		s->m[s->n_ms - 1].file = f;
		s->m[s->n_ms - 1].n_ts = 0;
		
		float4 mat[4];
		float scale, x, y, z;
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &mat[0].w, &mat[1].w, &mat[2].w);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &x, &y, &z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &scale);
		
		
		// make rotation
		make_matrix4(mat, x, y, z);
		
		// scale translation too
		for(int i = 0; i < 3; i++)
			mat[i].w /= scale;
		
		line++;
		int sf_id;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &sf_id);
		parseMesh(s, f, sf_id, mat, scale);
	}
	
	
	// triangles
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("SPHERES", buffer) == 0)
			break;
		
		if(nstrcmp("TRIANGLE", buffer) != 0)
			continue;
		
		s->n_ts++;
		if(s->n_ts == 1)
			s->t = (triangle *) malloc(sizeof(triangle));
		else
			s->t = (triangle *) realloc(s->t, s->n_ts * sizeof(triangle));
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->t[s->n_ts - 1].sf_id);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->t[s->n_ts - 1].point.x,
		                           &s->t[s->n_ts - 1].point.y,
		                           &s->t[s->n_ts - 1].point.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->t[s->n_ts - 1].v[0].x,
		                           &s->t[s->n_ts - 1].v[0].y,
		                           &s->t[s->n_ts - 1].v[0].z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->t[s->n_ts - 1].v[1].x,
		                           &s->t[s->n_ts - 1].v[1].y,
		                           &s->t[s->n_ts - 1].v[1].z);
		
		
		s->t[s->n_ts - 1].v[0] -= s->t[s->n_ts - 1].point;
		s->t[s->n_ts - 1].v[1] -= s->t[s->n_ts - 1].point;
		
		s->t[s->n_ts - 1].normal = normalize(cross(s->t[s->n_ts - 1].v[0],
		                                           s->t[s->n_ts - 1].v[1]));
	}
	
	// spheres
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		line++;
		if(nstrcmp("PLANES", buffer) == 0)
			break;
		
		if(nstrcmp("SPHERE", buffer) != 0)
			continue;
		
		s->n_ss++;
		if(s->n_ss == 1)
			s->s = (sphere *) malloc(sizeof(sphere));
		else
			s->s = (sphere *) realloc(s->s, s->n_ss * sizeof(sphere));
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->s[s->n_ss - 1].sf_id);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->s[s->n_ss - 1].center.x,
		                           &s->s[s->n_ss - 1].center.y,
		                           &s->s[s->n_ss - 1].center.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f", &s->s[s->n_ss - 1].radius);
	}
	
	// planes
	while(fgets(buffer, BUFFER_SIZE, fp))
	{
		if(nstrcmp("PLANE", buffer) != 0)
			continue;
		
		s->n_ps++;
		if(s->n_ps == 1)
			s->p = (plane *) malloc(sizeof(plane));
		else
			s->p = (plane *) realloc(s->p, s->n_ps * sizeof(plane));
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%d", &s->p[s->n_ps - 1].sf_id);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->p[s->n_ps - 1].point.x,
		                           &s->p[s->n_ps - 1].point.y,
		                           &s->p[s->n_ps - 1].point.z);
		
		line++;
		if(!fgets(buffer, BUFFER_SIZE, fp))
			parseError(s->infile, line);
		sscanf(buffer, "%f %f %f", &s->p[s->n_ps - 1].normal.x,
		                           &s->p[s->n_ps - 1].normal.y,
		                           &s->p[s->n_ps - 1].normal.z);
	}
	
	// store the pixel size
	s->c.px_size = 2. * sin(0.5 * s->c.fovy * PI / 180.) / (float) s->c.y_res;
	
	// make room for output
	s->c.out = (color *) malloc(s->c.x_res * s->c.y_res * sizeof(color));
	return;
}
